package gameEngine

import (
	"MetroBuilderGameJam/gameEngine/geom"
	"MetroBuilderGameJam/gameEngine/utils"
	"MetroBuilderGameJam/world"
	"fmt"
	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
	"reflect"
	"time"
)

type Game struct {
	name           string
	view           *Viewer
	gui            *Gui
	city           *world.City
	builder        *world.Builder
	isPaused       bool
	speed          utils.Speed
	prevUpdateTime time.Time

	figures           []utils.ItemFigure
	hoverItem         utils.ItemFigure
	prevHoverItem     utils.ItemFigure
	enterItem         utils.ItemFigure
	isGameOver        bool
	isMainMenuVisible bool
}

func NewGame(viewer *Viewer) *Game {
	g := &Game{
		name:              "Metro Builder",
		view:              viewer,
		speed:             utils.SpeedNormal,
		prevUpdateTime:    time.Now(),
		figures:           make([]utils.ItemFigure, 0, 1024),
		isMainMenuVisible: true,
	}

	viewer.DebugEnabled = false
	g.gui = NewGui(viewer.windowSize, g)
	g.gui.OnContinue = g.CloseMainMenu
	g.gui.OnRestartLevel = g.StartNewLevel

	g.StartNewLevel()
	g.ShowMainMenu()
	return g
}

func (g *Game) Update() error {
	dt := time.Since(g.prevUpdateTime).Seconds() * utils.GetSpeedCoef(g.speed)
	if g.isPaused && !g.isGameOver {
		dt = 0
	}

	x, y := ebiten.CursorPosition()
	hoverFigure := g.gui.InteractiveCursor(x, y)
	g.prevUpdateTime = time.Now()
	cursor, hoverGameFigure, enterFigure := g.interactiveCursor()

	g.view.DebugConsoleOutput = fmt.Sprintf("Cursor: (%d, %d), (%f, %f) ", x, y, cursor.X, cursor.Y)
	if hoverFigure == nil {
		hoverFigure = hoverGameFigure
	}
	if hoverFigure != nil {
		g.view.DebugConsoleOutput += fmt.Sprintf("Hover figure: %T ", hoverFigure.GetItem())
	}

	g.mainMenuUserControl()
	if g.isMainMenuVisible {
		g.gui.UserControl(hoverFigure, enterFigure)

		return nil
	}
	if g.isGameOver {
		utils.CommonTimer().Update(dt)

		return nil
	}
	if g.city.GetGameOverStation() == nil {
		g.userControl(cursor, hoverFigure, enterFigure)

		g.determineNewAvailableFieldForBuilder()
		g.city.Update(dt)
		g.gui.UpdateGameItems(g.city)
		utils.CommonTimer().Update(dt)
	} else {
		g.StartGameOver()
	}

	return nil
}

func (g *Game) cursorPosition() geom.Point {
	x, y := ebiten.CursorPosition()
	p, err := g.view.WindowPointToWorld(geom.IntPoint{X: x, Y: y})
	if err != nil {
		return geom.Point{}
	}

	return p
}

func (g *Game) findFigureUnderPoint(point geom.Point) utils.ItemFigure {
	for i := len(g.figures) - 1; i >= 0; i-- {
		f := g.figures[i]
		if f.GetCollider().Contains(point) {
			return f
		}
	}
	return nil
}

func (g *Game) interactiveCursor() (geom.Point, utils.ItemFigure, utils.ItemFigure) {
	cursor := g.cursorPosition()

	g.hoverItem = g.findFigureUnderPoint(cursor)
	g.enterItem = nil
	if g.hoverItem != nil && g.hoverItem.GetItem() != nil && (g.prevHoverItem == nil || !reflect.DeepEqual(g.hoverItem.GetItem(), g.prevHoverItem.GetItem())) {
		g.enterItem = g.hoverItem
	}
	g.prevHoverItem = g.hoverItem

	return cursor, g.hoverItem, g.enterItem
}

func (g *Game) userControl(cursor geom.Point, hoverFigure, enterFigure utils.ItemFigure) {
	g.gui.UserControl(hoverFigure, enterFigure)
	if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
		g.city.DragTrainPlan(cursor)
		if g.city.IsDragging() {
			g.city.TryAcceptTrainPlanToBranch(utils.FigureItemAs[world.BranchCompleted](hoverFigure))
		}
		if hoverFigure != nil && enterFigure == nil && !g.city.IsBuildingProcess() && !g.city.IsDragging() {
			g.city.StartDragTrain(utils.ItemAs[world.Train](hoverFigure.GetItem()), cursor)
			g.city.StartProjectBranchFromStation(utils.ItemAs[world.Station](hoverFigure.GetItem()))
			g.city.StartProjectBranchFromEdge(utils.ItemAs[world.BranchEdge](hoverFigure.GetItem()))
			g.city.ProjectToChangeCompletedBranch(utils.ItemAs[world.BranchCompleted](hoverFigure.GetItem()))
		}
		if g.city.IsBuildingProcess() {
			g.city.BuildRailRoadToggleStation(utils.FigureItemAs[world.Station](enterFigure))
			g.city.NewLineBuildPoint(&cursor)
		}
		pb := g.city.GetActiveProjectBranch()
		if pb != nil && pb.From() != nil {
			g.view.DebugConsoleOutput += fmt.Sprintf("Project branch: %T From: (%f, %f)", pb, pb.From().X, pb.From().Y)
		}
	} else {
		g.city.StopActiveItem()
	}
	if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
		g.isPaused = !g.isPaused
	}
	if inpututil.IsKeyJustPressed(ebiten.Key1) {
		g.speed = 1
	}
	if inpututil.IsKeyJustPressed(ebiten.Key2) {
		g.speed = 2
	}
	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
		g.city.InverseBuildingBranch()
	}
}

func (g *Game) Draw(screen *ebiten.Image) {
	g.view.HoverItem = g.hoverItem
	g.figures = g.view.Draw(g.city, screen)
	//drawer.DebugDrawerAnchors(screen)
	g.gui.Draw(screen)
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
	return outsideWidth, outsideHeight
}

func (g *Game) GetName() string {
	return g.name
}

func (g *Game) determineNewAvailableFieldForBuilder() {
	k := 2.3
	maxSize := g.view.CanvasSize().AsVec2().Div(k).ToSize()
	fieldSize := g.view.CanvasSizeOnScreen().AsVec2().Div(k).ToSize()
	if fieldSize.Width > maxSize.Width {
		fieldSize.Width = maxSize.Width
	}
	if fieldSize.Height > maxSize.Height {
		fieldSize.Height = maxSize.Height
	}
	g.builder.NewAvailableField(fieldSize)
}

func (g *Game) itemIsBranchProject(figure utils.ItemFigure) bool {
	if figure != nil && figure.GetItem() != nil {
		_, ok := figure.GetItem().(*world.BranchProject)
		return ok
	}
	return false
}

func (g *Game) StartGameOver() {
	utils.CommonTimer().Clear()
	utils.CommonTimer().AddCommand(1000, g.ShowMainMenu)
	g.isGameOver = true
	g.gui.CanContinue = false
}

func (g *Game) ShowMainMenu() {
	g.isMainMenuVisible = true
	g.gui.IsMainMenuVisible = true
}

func (g *Game) CloseMainMenu() {
	g.isMainMenuVisible = false
	g.gui.CloseMainMenu()
}

func (g *Game) StartNewLevel() {
	utils.CommonTimer().Clear()
	g.CloseMainMenu()
	g.gui.CanContinue = true

	g.isGameOver = false
	g.builder = world.NewSimpleBuilder(g.view.Center())
	g.city = world.NewCity(g.builder, world.NewPassengerFlowOrganizer())
	utils.CommonTimer().AddCommand(10000, g.city.TimeForNewStation)
	utils.CommonTimer().AddCommand(500, g.city.TimeForNewPassenger)
	utils.CommonTimer().AddCommand(153000, func() {
		if g.city.TimeForNewLine() {
			g.gui.notification = "You have got a new line with a train!"
		} else {
			g.gui.notification = "You have got a new train!"
		}

		g.ShowMainMenu()
	})

	g.speed = utils.SpeedNormal
}

func (g *Game) mainMenuUserControl() {
	if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
		if g.isMainMenuVisible && g.gui.CanContinue {
			g.CloseMainMenu()
		} else {
			g.ShowMainMenu()
		}
	}
}
